package edu.umbc.cs.cmsc471.mastermind;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;


public class Player {
	public Socket _socket;
	public BufferedReader in;
	public PrintWriter out;
	
	public int _numPegs;
	public String[] _colorSpace;
	
	public ArrayList<GuessPair> guesses = new ArrayList<GuessPair>();
	public Random r;
	public int maxEligSize;
	public int maxGenSize;
	public int populationSize;
	public String[] code;
	
	public final static String IP = "127.0.0.1"; //this should be the IP of the tournament
	public final static int DEFAULTPORT = 4444;  //this should be the port of the tournament

	public Player(int port) {
		//open the various streams
		try {
			_socket = new Socket(IP, port);
			in = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
			out = new PrintWriter(_socket.getOutputStream());
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//read the basic info
		readProblemDescription();
		r = new Random();
		
		maxEligSize = 60;
		maxGenSize = 150;
		populationSize = 150;
		ArrayList<EligGuessPair> eligGuesses = new ArrayList<EligGuessPair>();
		this.code = new String[_numPegs];
		boolean done = false;
		
		int max = 0;
		int maxBlack = 0;

		//start writing guesses and getting back results
		String response = null;
		while(response == null || Integer.parseInt(response.split(",")[0]) != _numPegs && done)
		{
			//System.out.println(_numPegs + " " + _colorSpace.length);
			int i = 1;
			done = true;
			//play initial guess
			for(int j = 0; j < _numPegs; j++)
			{
				int k = 0; int l = 0;
				if (j < (((_numPegs / 2) - 1) * 2))
				{
					this.code[j] = _colorSpace[l];
					if (k == 0)
					{
						k++;
					}
					else
					{
						k = 0;
						l++;
					}
				}
				else
				{
					this.code[j] = _colorSpace[j - (_numPegs/2) + 1];
				}
			}
			
			/*
			for (int m = 0; m < _numPegs; m++)
			{
				System.out.print(this.code[m].toString());
			}
			*/
			
			
			Code myGuess = sendGuess(this.code);
			response = receiveResponse();
			System.out.println(response);
			guesses.add(new GuessPair(myGuess, response));
			
			while(response == null ||Integer.parseInt(response.split(",")[0]) != _numPegs)
			{
				i++;
				//System.out.println("Inside GA");
				int h = 1;
				eligGuesses.clear();
				
				//Initialize population
				ArrayList<EligGuessPair> myPop = initPopulation(populationSize);
				
				/*
				for (int j = 0; j < myPop.size(); j++)
				{
					System.out.println(myPop.get(j).getCode());
				}
				*/
				
				
				while (h < maxGenSize && eligGuesses.size() < maxEligSize)
				{	
					this.findPopFitness(myPop, i);
					sortPop(myPop);
					myPop = this.crossPopulation(myPop);
					
					for (int j = 0; j < myPop.size(); j++)
					{
						EligGuessPair newEligGuess = this.fitnessFunction(myPop.get(j).getCode(), i);
						if (newEligGuess != null)
						{
							boolean isFound = false;
							for (int k = 0; k < guesses.size(); k++)
							{
								if (guesses.get(k).getCode().toString().equals(newEligGuess.getCode().toString()))
								{
									isFound = true;
								}
							}
							
							if (!isFound)
							{
								eligGuesses.add(newEligGuess);
							}
						}
					}
					h++;
				}
				
				sortPop(eligGuesses);
				
				/*
				for (int k = 0; k < eligGuesses.size(); k++)
				{
					System.out.println(eligGuesses.get(k).getCode().toString());
				}
				*/
				
				if (eligGuesses.size() != 0)
				{
					Code myNewGuess = sendGuess(eligGuesses.get(0).getCode().toString().split(","));
					response = receiveResponse();
					System.out.println(response);
					//System.out.println(i);
					guesses.add(new GuessPair(myNewGuess, response));
										
					if (max < _numPegs)
					{
						for (int k = 0; k < guesses.size(); k++)
						{
							int tempMax = 0;
							String[] test = guesses.get(k).getResult().split(",");
							maxBlack = Integer.parseInt(test[0]);
							tempMax = Integer.parseInt(test[0]) + Integer.parseInt(test[1]);
							if (tempMax > max)
							{
								max = tempMax;
							}
						}
						
						for (int k = 0; k < guesses.size(); k++)
						{
							int tempMax;
							String[] test = guesses.get(k).getResult().split(",");
							tempMax = Integer.parseInt(test[0]) + Integer.parseInt(test[1]);
							if (tempMax < max)
							{
								guesses.remove(k);
								k--;
							}
						}
					}
					
					if (max == _numPegs)
					{
						for (int k = 0; k < guesses.size(); k++)
						{
							int tempMaxBlack = 0;
							String[] test = guesses.get(k).getResult().split(",");
							maxBlack = Integer.parseInt(test[0]);
							if (tempMaxBlack > maxBlack)
							{
								maxBlack = tempMaxBlack;
							}
						}
						
						for (int k = 0; k < guesses.size(); k++)
						{
							int tempMaxBlack;
							String[] test = guesses.get(k).getResult().split(",");
							tempMaxBlack = Integer.parseInt(test[0]);
							if (tempMaxBlack < maxBlack)
							{
								guesses.remove(k);
								k--;
							}
						}
					}
				}
			}
		}
	}
	
	public void sortPop(ArrayList<EligGuessPair> pop)
	{
		Collections.sort(pop, new Comparator<EligGuessPair>() {
	        @Override public int compare(EligGuessPair p1, EligGuessPair p2) {
	            return p2.getFitness() - p1.getFitness();
	        }

	    });
	}
	
	
	public ArrayList<EligGuessPair> crossPopulation(ArrayList<EligGuessPair> pop)
	{
		int crossPoint = _numPegs / 2;
		ArrayList<EligGuessPair> newPop = new ArrayList<EligGuessPair>();
		
		for (int i = 0; i < pop.size()/2; i++)
		{
			int j = r.nextInt(2);
			
			if (j == 1)
			{
				String[] a = pop.get(i).getCode().toString().split(",");
				String[] b = pop.get(i+1).getCode().toString().split(",");
				String[] c = new String[_numPegs];
				String[] d = new String[_numPegs];
				
				for (int k = 0; k < crossPoint; k++)
				{
					c[k] = a[k];
					d[k] = b[k];
				}
				
				for (int k = crossPoint; k < _numPegs; k++)
				{
					c[k] = b[k];
					d[k] = a[k];
				}
				
				
				EligGuessPair codeA = new EligGuessPair(new Code(_colorSpace, c));
				EligGuessPair codeB = new EligGuessPair(new Code(_colorSpace, d));
				
				newPop.add(codeA);
				newPop.add(codeB);
			}
			
			else
			{
				newPop.add(pop.get(i));
				newPop.add(pop.get(i+1));
			}
		}
		
		/*
		for (int i = 0; i < newPop.size(); i++)
		{
			System.out.println(newPop.get(i).getCode());
		}
		*/
		
		return newPop;
	}
	
	
	public void findPopFitness(ArrayList<EligGuessPair> pop, int j)
	{
		for (int i = 0; i < pop.size(); i++)
		{
			pop.get(i).setFitness(this.findFitness(pop.get(i).getCode(), j));
		}
	}
	
	public int findFitness(Code code, int i)
	{
		int a = 3; int b = 1;
		int blackFitness = 0;
		int whiteFitness = 0;
		
		for (int j = 0; j < guesses.size(); j++)
		{
			//System.out.println(j);
			String[] stringParse = this.makeFitness(guesses.get(j).getCode(), code).split(",");
			String[] origStringParse = guesses.get(j).getResult().split(",");
			blackFitness += Integer.parseInt(stringParse[0]) - Integer.parseInt(origStringParse[0]);
			whiteFitness += Integer.parseInt(stringParse[1]) - Integer.parseInt(origStringParse[1]);
		}
		
		int fitness = (a * blackFitness) + whiteFitness + (b * _numPegs * (i - 1));
		return fitness;
	}
	
	public EligGuessPair fitnessFunction(Code code, int i)
	{
		int a = 3; int b = 1;
		int blackFitness = 0;
		int whiteFitness = 0;
		boolean isEligible = false;
		
		//int j = guesses.size() - 1;
		for (int j = 0; j < guesses.size(); j++)
		{
			//System.out.println(j);
			String[] stringParse = this.makeFitness(guesses.get(j).getCode(), code).split(",");
			String[] origStringParse = guesses.get(j).getResult().split(",");
			blackFitness += Integer.parseInt(stringParse[0]) - Integer.parseInt(origStringParse[0]);
			whiteFitness += Integer.parseInt(stringParse[1]) - Integer.parseInt(origStringParse[1]);
		}
		
		if ((blackFitness + whiteFitness) >= 0)
		{
			isEligible = true;
			int fitness = (a * blackFitness) + whiteFitness + (b * _numPegs * (i - 1));
			//System.out.println(fitness);
			EligGuessPair eligGuess = new EligGuessPair(code, isEligible, fitness);
			return eligGuess;
		}
		
		return null;
	}
	
	public String makeFitness(Code originalGuess, Code myCode)
	{
		String test = originalGuess.guess(myCode);
		//System.out.println(test);
		return test;
	}
	
	public ArrayList<EligGuessPair> initPopulation(int newPopSize)
	{
		ArrayList<EligGuessPair> newPop = new ArrayList<EligGuessPair>();
		int i = 0;
		while(i < newPopSize)
		{	
			//System.out.println(i);
			if (i > 0)
			{
				//System.out.println(i);
				EligGuessPair test = new EligGuessPair(new Code(_colorSpace, _numPegs, true));
				boolean found = false;
				for (int j = 0; j < newPop.size(); j++)
				{
					if (test.getCode().toString().equals(newPop.get(j).getCode().toString()))
					{
						found = true;
					}
				}
				
				if (found == false)
				{
					newPop.add(test);
					i++;
				}
			}
			else
			{
				newPop.add(new EligGuessPair(new Code(_colorSpace, _numPegs, true)));
				i++;
			}
		}
		return newPop;
	}
	
	//parse the numPegs and set of colors for guessing
	public void readProblemDescription() {
		String description;
		try {
			description = in.readLine();
			while(description == null)
				description = in.readLine();
			String[] split1 = description.split(";");
			_numPegs = Integer.parseInt(split1[0]);
			_colorSpace = split1[1].split(",");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
		

	//make a random guess
	public Code sendGuess(String[] newCode) {
		//out.println((new Generator(_numPegs, _colorSpace)).generate());
		Code guess = new Code(_colorSpace, newCode);
		out.println(guess);
		out.flush(); //REALLY IMPORTANT TO FLUSH
		return guess;
	}

	//parse a response (I don't actually do any parsing here, you need to do that)
	public String receiveResponse() {
		String response;
		try {
			response = in.readLine();
			while(response == null)
				response = in.readLine();
			return response;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	@SuppressWarnings("unused")
	public static void main(String[] args) {
		Player player = new Player(Player.DEFAULTPORT);
	}
	
}

